#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#include "body.h"
#include "common.h"

#include "integrate0.h"
#include "integrate1.h"
#include "integrate2.h"
#include "integrate3.h"
#include "integrate4.h"
#include "integrate5.h"
#include "integrate6.h"
#include "energy.h"

int main(int argc, char* argv[])
{
	cudaSetDevice(1);

    bodyList* bl = readBodyList();
	//convertPinnedBodyList(bl);

    int method = 1;
    int iterations = 1;
	float dt = 1.0f;
    bool time = false;
    bool output = false;
	bool show_energy = false;
    int output_stride = 1;
    unsigned int threadNum = 1;
    opterr = 0;

	// read program arguments
	int c;
	while ((c = getopt(argc, argv, "i:d:m:s:toep:")) != -1)
	{
		switch (c)
		{
			case 'i':
				iterations = atoi(optarg);
				break;
			case 'd':
				dt = atof(optarg);
				break;
			case 'm':
				method = atoi(optarg);
				break;
			case 's':
				output_stride = atoi(optarg);
				break;
			case 't':
				time = true;
				break;
			case 'o':
				output = true;
				break;
			case 'e':
				show_energy = true;
				break;
            case 'p':
                threadNum = atoi(optarg);
                break;
			default:
				break;
		}
	}

	//if (!output)
	//	output_stride = iterations;

    // initialise cuda timer
    cudaEvent_t start, stop;
    cudaEventCreate(&start); cudaEventCreate(&stop);
    cudaEventRecord(start, 0);

    int iterations_left = iterations;
    while (iterations_left > 0)
    {
        int curr_iterations = (output_stride > iterations_left) ?
            iterations_left : output_stride;
        iterations_left -= curr_iterations;

        // run kernels
        switch (method)
        {
            case 0:
                integrate0(bl, curr_iterations, threadNum, dt);
                break;
            case 1:
                integrate1(bl, curr_iterations, dt);
                break;
            case 2:
                integrate2(bl, curr_iterations, dt);
                break;
            case 3:
                integrate3(bl, curr_iterations, dt);
                break;
            case 4:
                integrate4(bl, curr_iterations, dt);
                break;
            case 5:
                integrate5(bl, curr_iterations);
                break;
            case 6:
                integrate6(bl, curr_iterations, dt);
                break;
            default:
                printf("Invalid method\n");
                break;
        }

		// write output to file
		if (output)
		{
			char output_filename[50];
			sprintf(output_filename, "output%.6i.dat", (iterations - iterations_left)/output_stride - 1);
			printf("output%.6i.dat\n", (iterations - iterations_left)/output_stride - 1);
			fflush(stdout);
			writeBodyList(bl, output_filename);
		}

		if (show_energy)
		{
			float K, W;
			energy(bl, &K, &W);
			printf("i: %d K: %e W: %e T: %e\n",
				iterations - iterations_left, K, W, K+W);
		}
	}

    // get cuda time
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    float elapsed_time;
    cudaEventElapsedTime(&elapsed_time, start, stop);
    cudaEventDestroy(start); cudaEventDestroy(stop);

    if (time)
        printf("%f\n", elapsed_time);
    else
        writeBodyList(bl);

    freeBodyList(bl);

    return 0;
}
